In Session 5, you encountered a model for a production company that used multiple plants to produce their product. What you should note about that model is that while each of the different plants could be used to produce the products individually, all the selling and inventory was handled collectively, as a single source, for the whole company. You will now upgrade that model to allow each plant to sell the products, and maintain inventory individually. Furthermore, in order to fulfill the demand in the most efficient manner, the products can be shipped between the plants as needed.
To upgrade the model you will need to include two new alias indexes, toplant, and fromplant, which will represent the source and destination plants. You will also update the Inventory and Sales variables to include the plant index, as each plant can now sell the products and maintain inventory independent of each other.
Models that allow shipping between locations are sometimes called transportation or distribution models. Typically, in transportation models, you have sources with certain availability, destinations with certain requirements, and you need to ship the products from these sources to these destinations. In some cases, you have transporation models with multiple levels. For example, there can be shipments from plants to depots, and then from the depots to retail stores.
Another group of distribution models are transshipment models. These models typically arise when you have multiple locations that both produce the goods and also act as demand centers. Since there are no specific sources and destinations, you can ship from any one location to any of the other locations.
Alias indexes are useful when you need to define a vector, which uses the same index more than once, as a subscript. When shipping products between plants you need to create a variable vector, representing how much to ship between the plants. Since the source plants and the destination plants come from the same set of plants, you need two alias indexes for the plants. The first alias index is needed to represent the source plants, and the second alias index is needed to represent the destination plants.
Sometimes, when working with multi-dimensional vector variables you will encounter cases where not all elements of the vector are valid, or have a meaning. For example, in transshipment models it would make little sense to ship a product from a certain plant back to that same plant. In these cases, you can use a WHERE condition on the variable to remove unnecessary elements.
For example, in a transshipment model you can eliminate the possibility of shipping to the same location by defining the variable as follows:
VARIABLES Ship[fromplant,toplant] WHERE (fromplant <> toplant);
In this case, the condition (fromplant <> toplant) removes all of the vector elements, where the source plant is the same as the destination plant.
In some cases, elements need to be excluded that are not based on the values of the indexes. They then must be based on some data vector in the model. Typically, you have a cost vector containing how much it costs to ship between the plants. For those plants, if shipping between is not feasible, you can enter a special value for the cost, such as a zero, to be used to identify them. Then, in the variable definition, you can use this data vector to exclude the shipping routes that are not feasible as follows:
VARIABLES Ship[fromplant,toplant] WHERE (ShipCost[fromplant,toplant] > 0);
When working with transshipment models you need to ensure that the amount of products shipped to a plant, plus how much is produced and pulled from inventory, is equal to how much is shipped from the plant, plus how much is sold and put back into inventory. In short, everything that goes into the plant must be equal to everything that goes out of the plant. This kind of constraint is typically called a plant balance constraint. Here is an example of a simple plant balance constraint:
PlantBal[plant, product, month]: Produce + Inventory[month-1] + SUM(fromplant: Ship[fromplant, toplant:=plant]) = Sales + Inventory + SUM(toplant: Ship[fromplant:=plant, toplant]);
You will notice that this constraint is similar to the inventory balance constraint you have encountered in previous sessions. The only difference is that now you have to take into account that we are shipping to and from each plant by entering a summation over each plant for the Ship variable.
The index assignment 'toplant:=plant', in the first summation, allows us to specify that the toplant subscript should take the value of the plant subscript for the PlantBal constraint. This summation adds together all the shipments from each of the plants to the particular plant in that constraint. In similar manner, the index assignment 'fromplant:=plant', in the second summation, specifies that the fromplant subscript should take the value of the plant subscript.
In this session, a new model will be created where each plant now acts as a separate demand center for the products, and can also keep inventory. You will use the model you created in Session 5, and make the necessary additions and updates to it.
Since each plant can sell the products, we now have a different demand for each plant, as well as for each product, and each month. The demand is given in the dbtable below:
Plant | Product | Jan | Feb | Mar | Apr |
---|---|---|---|---|---|
p1 | A1 | 4300 | 4200 | 6400 | 5300 |
A2 | 4500 | 5400 | 6500 | 7200 | |
A3 | 5400 | 6700 | 7800 | 8200 | |
p2 | A1 | 5100 | 6200 | 5400 | 7600 |
A2 | 6300 | 7100 | 5200 | 6300 | |
A3 | 4800 | 6500 | 5000 | 7200 | |
p3 | A1 | 4100 | 6100 | 4700 | 5800 |
A2 | 5300 | 5200 | 5700 | 4100 | |
A3 | 4200 | 4100 | 5200 | 6300 | |
p4 | A1 | 4300 | 4100 | 5300 | 4500 |
A2 | 5300 | 6400 | 4200 | 6200 | |
A3 | 5600 | 5200 | 3800 | 4100 |
This data has three dimensions, plants, products and months. In linear programming models it is quite typical to have data with multiple dimensions, possibly up to eight or more. In the next session, we will update the demand data to include one more dimension; machines, creating a four dimensional vector.
The inventory capacity is now different for each plant. We have four inventory capacity values, one for each plant, 800, 400, 500, and 400 respectively.
Since we now have multiple plants, each of which can maintain inventory, we now have different inventory costs for each plant, and each product. The new cost values for the inventory are shown here below:
Inventory Cost | A1 | A2 | A3 |
---|---|---|---|
p1 | $8.50 | $7.00 | $6.50 |
p2 | $9.80 | $9.80 | $9.80 |
p3 | $7.50 | $7.50 | $7.50 |
p4 | $9.30 | $8.00 | $6.50 |
Finally, since we are allowing shipments between the plants there are certain costs involved for shipping a product, as shown in the dbtable below:
Shipping Cost | p1 | p2 | p3 | p4 |
---|---|---|---|---|
p1 | - | $15.00 | $21.00 | $13.00 |
p2 | $16.00 | - | $12.00 | $12.00 |
p3 | $14.00 | $17.00 | - | $15.00 |
p4 | $21.00 | $13.00 | $10.00 | - |
As you can see, there are no values in the dbtable where the source plant is same as the destination plant. This is because there is no benefit in shipping back to the same plant.
Listed below is the entire model formulation for Planning6. The additions to the model are highlighted in boldface in order to make it easy for you to see the changes from the model in Session 5.
TITLE Production_Planning6; INDEX product := (A1, A2, A3); month := (Jan, Feb, Mar, Apr); plant := (p1, p2, p3, p4); fromplant := plant; toplant := plant; DATA Price[product] := (120.00, 100.00, 115.00); Demand[plant, product, month] := DATAFILE("Demand6.dat"); ProdCost[plant, product] := DATAFILE("ProdCost.dat"); ProdRate[plant, product] := DATAFILE("ProdRate.dat"); ProdDaysAvail[month] := (23, 20, 23, 22); InvtCost[plant, product] := DATAFILE("InvtCost.dat"); InvtCapacity[plant] := (800, 400, 500, 400); ShipCost[fromplant, toplant] := DATAFILE ("ShipCost.dat"); VARIABLES Produce[plant, product,month] -> Prod; Inventory[plant, product, month] -> Invt; Sales[plant,product, month] -> Sale; Ship[product, month, fromplant, toplant] WHERE (fromplant <> toplant); MACROS TotalRevenue := SUM(plant, product,month: Price * Sales); TotalProdCost := SUM(plant, product, month: ProdCost* Produce); TotalInvtCost := SUM(plant, product, month: InvtCost * Inventory); TotalShipCost := SUM(product, month, fromplant,toplant: ShipCost * Ship); TotalCost := TotalProdCost + TotalInvtCost + TotalShipCost; MODEL MAX Profit= TotalRevenue - TotalCost; SUBJECT TO ProdCapacity[plant, month] -> PCap: SUM(product: Produce / ProdRate) <= ProdDaysAvail; PlantBal[plant, product, month] -> PBal: Produce + Inventory[month-1] + SUM(fromplant:Ship[fromplant, toplant:=plant]) = Sales + Inventory + SUM(toplant: Ship[fromplant:=plant,toplant]); MaxInventory[plant, month] -> MaxI: SUM(product: Inventory) <= InvtCapacity; BOUNDS Sales < Demand ; END
Start the MPL application.
Choose File | Open and open the model from the previous session Planning5.mpl.
Choose File | Save As to save it as a new model file Planning6.mpl.
Change the title for the model to reflect that you are working with the Planning6 model:
TITLE Production_Planning6;
In this session, you are going to expand the model to allow shipments between the plants. Alias indexes are useful when you need to define a vector, which refers to the same index more than once, as a subscript. Alias indexes are an exact copy of a previously defined index.
In this case, you are creating a vector variable representing how much to ship between the plants. Which means you need two alias indexes to represent the source, and destination plants. Add the following definitions for the two new alias indexes that you will call fromplant and toplantat the end of the INDEX section:
INDEX product := (A1, A2, A3); month := (Jan, Feb, Mar, Apr); plant := (p1, p2,p3, p4); fromplant := plant; toplant := plant;
We now have a different demand for each plant. The definition for the Demand data vector needs to be upgraded to include the plant index. In the model editor, add the index plant to the Demand data vector and change the file name to Demand6.dat.
DATA Price[product] := (120.00, 100.00, 115.00); Demand[plant, product, month] := DATAFILE("Demand6.dat");
Since the Demand vector now has three indexes, the data file for it needs to be updated. Therefore, you are creating a new data file called 'Demand6.dat' using the data values from the dbtable in the problem description given earlier in this session. Enter the data values to the data file as follows:
! ! Demand6.dat - Demand for each product and each plant ! ! Demand[plant,product,month]: ! ! Jan Feb Mar Apr ! --------------------------- !plant 1: 4300, 4200, 6400, 5300, 4500, 5400, 6500, 7200, 5400, 6700, 7800, 8200, !plant 2: 5100, 6200, 5400, 7600, 6300, 7100, 5200, 6300, 4800, 6500, 5000, 7200, !plant 3: 4100, 6100, 4700, 5800, 5300, 5200, 5700, 4100, 4200, 4100, 5200, 6300, !plant 4: 4300, 4100, 5300, 4500, 5300, 6400, 4200, 6200, 5600, 5200, 3800, 4100
When you have three dimensional data in data files, list the data values in same order that the indexes were defined in data vector for the model. For example, in the above Demand6 data file the leftmost index is the plant index, followed by the product and the month index.
Since you can now store inventory at each plant, you need to update the inventory cost and the inventory capacity data to include the plant index. In the model editor, add the index plant to the declaration of the InvtCost data vector and the InvtCapacity data constant. Then, for the InvtCost data vector remove the list of numbers and replace it with the keyword DATAFILE and the file name 'InvtCost.dat.' For the InvtCapacity remove the single value 800 and replace it with the list of four values, one for each plant, taken from the problem description earlier in this session.
InvtCost[plant, product] := DATAFILE("InvtCost.dat"); InvtCapacity[plant] := (800, 400, 500, 400);
Next, you need to create a new data file called 'InvtCost.dat' using the cost values from the inventory cost dbtable in the problem description. Enter the data values into the data file as follows:
! ! InvtCost.dat - Inventory cost per item a month ! ! InvtCost[plant,product]: ! ! A1 A2 A3 ! ----------------------- 8.50, 7.00, 6.50 9.80, 9.80, 9.80 7.50, 7.50, 7.50 9.30, 8.00, 6.50
There are certain costs involved in shipping products between plants. In the model editor, add a new data vector called ShipCost defined over the two alias indexes fromplant and toplant followed by the keyword DATAFILE and the file name ShipCost.dat.
ShipCost[fromplant, toplant] := DATAFILE("ShipCost.dat");
Next, you need to create a new data file called ShipCost.dat containing the cost figures for shipping between the plants provided in the problem description. Enter the data values for the data file as follows:
! ! ShipCost.dat - Shipping costs from plant to plant ! ! ShipCost[fromplant, toplant] ! 0, 15.00, 21.00, 13.00, 16.00, 0, 12.00, 12.00, 14.00, 17.00, 0, 15.00, 21.00, 13.00, 10.00, 0,
We are allowing shipping between the plants, therefore, you need to create a new variable that decides how much is to be shipped of each product per month. This variable vector will be defined over the alias indexes fromplant and toplant as well as the product and the month index. Add the following definition for the Ship variable vector to the VARIABLES section:
Ship[product, month, fromplant, toplant] WHERE (fromplant <> toplant);
Since we do not want to ship a product from a plant back to the same plant, we are using a WHERE condition to remove all of the vector elements where the source plant is the same as the destination plant.
In the MACROS section add a new macro definition for the total shipping cost called TotalShipCost and update the TotalCost macro to include the new macro as follows:
TotalShipCost := SUM(product, month, fromplant, toplant: ShipCost * Ship); TotalCost := TotalProdCost + TotalInvtCost + TotalShipCost;
Note that the actual objective function definition does not need to be changed as the TotalCost macro contains all the changes.
Since we now have shipments allowed between the plants, you need to upgrade the inventory balance constraint from the previous model to a plant balance constraint. First, change the name of the constraint from InvtBal to PlantBal and add the index plant to the declaration. Next, since the constraint is now declared over the index plant we do not have to sum over the index plant for the Produce variable anymore.
PlantBal[plant, product, month] -> PBal: Produce + Inventory[month-1] + SUM (fromplant:Ship[fromplant, toplant:=plant]) = Sales + Inventory + SUM(toplant: Ship[fromplant:=plant, toplant]);
On the left hand side, where we bring together everything that is going into the plant, add a summation to add together all the shipments from each of the other plants to the current plant for the constraint. Inside the summation, enter the Ship variable with the destination plant taking the value of the current plant index.
On the right hand side, where we bring together everything that is going out of the plant add another summation to add together all the shipments from the current plant to each of the other plants. Inside the summation enter the Ship variable this time with the source plant taking the value of the current plant index for the constraint.
The next step is to solve the Planning6 model by choosing Solve CPLEX from the Run menu. If everything goes well MPL will display the message "Optimal Solution Found". If there is an error message window with a syntax error please check and compare the formulation you entered with the model detailed earlier in this session.
You will use the model definitions window again as in Session 5 to look at the parts of the solution that we are interested in. To open the model definitions window for the Planning6 model choose Model Definitions from the View menu.
The Model Definitions Tree Window for the Planning6 Model
To look at the values for the Produce variable, either double-click on the Produce item in the tree, or select it and then press the View button. This will display a window containing only the values for the Produce variable as shown below.
VARIABLE Produce[plant,product,month] : plant product month Activity Reduced Cost ----------------------------------------------------------- p1 A1 Jan 4300.0 0.0 p1 A1 Feb 4200.0 0.0 p1 A1 Mar 6400.0 0.0 p1 A1 Apr 5300.0 0.0 p1 A2 Jan 1080.0 0.0 p1 A3 Jan 5400.0 0.0 p1 A3 Feb 5220.0 0.0 p1 A3 Mar 4590.0 0.0 p1 A3 Apr 5130.0 0.0 p2 A1 Jan 5100.0 0.0 p2 A1 Feb 6200.0 0.0 p2 A1 Mar 5400.0 0.0 p2 A1 Apr 7600.0 0.0 p2 A2 Jan 6177.3 0.0 p2 A2 Feb 3927.3 0.0 p2 A2 Mar 5931.8 0.0 p2 A2 Apr 3681.8 0.0 p3 A1 Jan 4100.0 0.0 p3 A1 Feb 6100.0 0.0 p3 A1 Mar 4700.0 0.0 p3 A1 Apr 5800.0 0.0 p3 A3 Jan 4166.7 0.0 p3 A3 Feb 1933.3 0.0 p3 A3 Mar 3766.7 0.0 p3 A3 Apr 2733.3 0.0 p4 A1 Jan 3850.0 0.0 p4 A1 Feb 2828.6 0.0 p4 A1 Mar 5300.0 0.0 p4 A1 Apr 4500.0 0.0 p4 A3 Jan 5600.0 0.0 p4 A3 Feb 5200.0 0.0 p4 A3 Mar 4677.3 0.0 p4 A3 Apr 4836.4 0.0 -----------------------------------------------------------
As you can see, the production is now distributed between the different plants in a more cost effective manner. Certain products are clearly better to produce at particular plants due to taking into account the production cost and the shipping cost. For example, product A2 is produced in plants p1 and p2, but not p3 and p4, while product A3 is produced in plants p1, p3 and p4. Product A1 is most economically produced in all of the plants.
If you go to the tree window again and open up a window for the Ship variable you will get the following solution values:
VARIABLE Ship[product,month,fromplant,toplant] : product month fromplant toplant Activity Reduced Cost ------------------------------------------------------------------------ A2 Mar p2 p4 331.8 0.0 A3 Mar p4 p3 877.3 0.0 A3 Apr p4 p3 736.4 0.0 ------------------------------------------------------------------------
As you can see the model proposes that we ship product A2 from plant p2 to plant p4. In the same manner, product A3 is shipped from plant p4 to p3. Clearly, plants p2 and p4 have extra capacity at lower cost that can be used to produce goods that plants p4 and p3 need.